#include "ToolBox/gl/shader.h"


Shader::Shader(std::string aVertexPath, std::string aFragmentPath) : vsPath(aVertexPath), fsPath(aFragmentPath)
{
	Init();
}

Shader::~Shader()
{
    glDeleteShader(vShader);    // Destruction du vertex shader
    glDeleteShader(fShader);    // Destruction du fragment shader
    glDeleteProgram(program);   // Destruction du programme
}

int Shader::Init()
{
    InitShader(fShader, GL_FRAGMENT_SHADER, fsPath);    //  Initialisation du Fragment Shaders
    InitShader(vShader, GL_VERTEX_SHADER, vsPath);        //  Initialisation du vertex Shader

    CreateProgram();    // Création du programme à partir des 2 shaders
    LinkProgram();      // Linkage

    return true;
}

int Shader::CreateProgram()
{
    program = glCreateProgram();    // Création du programme

    glAttachShader(program, vShader);   // On attache le vertex Shader compilé au programme
    glAttachShader(program, fShader);   // On attache le fragment Shader compilé au programme
}

int Shader::LinkProgram()
{
    // Linkage du program

    BindAttributesLocation();

    glLinkProgram(program);


    // On vérifie que le link c'est bien passé

    GLint link(0);
    glGetProgramiv(program, GL_LINK_STATUS, &link);

    if(link != GL_TRUE)
    {
        // Récupération de la taille de l'erreur

        GLint tailleErreur(0);
        char *erreur(NULL);

        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &tailleErreur);


        // Allocation de l'erreur

        erreur = new char[tailleErreur + 1];


        // Copie de l'erreur dans la chaine de caractères

        glGetProgramInfoLog(program, tailleErreur, &tailleErreur, erreur);
        erreur[tailleErreur] = '\0';


        // Affichage de l'erreur

        std::cout << "Erreur lors du link du program : " << erreur << std::endl;

        // On retourne false

        delete[] erreur;
        return false;
    }


    // Tout s'est bien passée, on retourne true

    init = true;
}


int Shader::CompileShader(GLuint shader, GLenum typeShader, std::string filePath)
{
	// Ouverture du fichier source

	std::string codeSource, ligneCodeSource;
	std::ifstream fichierSource(filePath.c_str());

	// On test l'ouverture du fichier

	if(!fichierSource)
	{
		std::cout << "Erreur le fichier : " << filePath << " n'existe pas" << std::endl;
		glDeleteShader(shader);

		return false;
	}

	// Si le fichier existe et qu'il est ouvert, alors on peut lire son contenu

	while(getline(fichierSource, ligneCodeSource))
	{
		codeSource += ligneCodeSource + '\n';
	}

	fichierSource.close();

	// Compilation du shader

	GLint erreurCompilation(0), tailleErreur(0);
	const GLchar* chaineCodeSource = codeSource.c_str();

	glShaderSource(shader, 1, &chaineCodeSource, NULL);
	glCompileShader(shader);


	// Vérification de la compilation

	glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

	if(erreurCompilation != GL_TRUE)
	{
		// Récupération de la taille de l'erreur

		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tailleErreur);


		// Allocation d'une chaine de caractères

		char *erreur = new char[tailleErreur + 1];
		erreur[tailleErreur] = '\0';


		// Récupération de l'erreur

		glGetShaderInfoLog(shader, tailleErreur, &tailleErreur, erreur);


		// Affichage de l'erreur

		std::cout << "Erreur de compilation du shader (" <<  typeShader << ") " << erreur << std::endl;


		// On libère la mémoire et on retourne false

		delete[] erreur;
		return false;
	}


	// Tout s'est bien passé on retourne true

	return true;
}



void Shader::InitShader(GLuint & shader, GLenum typeShader, std::string filePath)
{
	shader = glCreateShader(typeShader);
	CompileShader(shader, typeShader, filePath);
}


void Shader::DefineAttributeLocation(int indiceAttrib, std::string idAttrib)
{
    glBindAttribLocation(program, indiceAttrib, idAttrib.c_str());
}

void Shader::BindAttributesLocation()
{
	/*! Variables d'entrées du shader */
	
	glBindAttribLocation(program, 0, "in_Vertex");
	glBindAttribLocation(program, 1, "in_Color");
	glBindAttribLocation(program, 2, "in_TexCoord0");
	glBindAttribLocation(program, 3, "in_Normal");

}

void Shader::UseProgram()
{
	glUseProgram(program);
}

void Shader::DisableProgram()
{
    glUseProgram(0);
}
